#region Copyright

// AUTHOR: Omar Zabala
// Copyright (c) 2011, TheShieldStore.com
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
// 
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation
//  and/or other materials provided with the distribution. Neither the name of the TheShieldStore.com nor the names of its contributors may be used to endorse
//  or promote products derived from this software without specific prior written permission. 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES,  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
//  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#endregion

using System.IO.Ports;
using System.Text;
using SecretLabs.NETMF.Hardware.NetduinoPlus;

namespace DisplayShield
{
    public class DisplayShield
    {
        private readonly byte[] _res = new byte[1];
        public SerialPort SerialCom;

        /// <summary>
        ///   Driver class for the 4D Systems 4Display Shield, any of them
        /// </summary>
        public DisplayShield()
        {
            SerialCom = new SerialPort(SerialPorts.COM1, 115200);
            SerialCom.Open();

            // initialize the screen, the 0x55 command syncs the two devices' (screen and netduino) baud rate. the screen takes the netduino's baud rate
            SerialCom.Write(new[] {(byte) Command.GSGC_AUTOBAUD}, 0, 1);
        }

        /// <summary>
        ///   Clears the screen setting it to the background color.
        /// </summary>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool Clear()
        {
            return Send(new[] {(byte) Command.GSGC_CLS});
        }

        /// <summary>
        ///   Determines whether the objects are filled or not, this does not apply to polygons.
        /// </summary>
        /// <param name = "filled">Whether you want the object filled or not.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool FilledObjects(bool filled)
        {
            if (filled)
                return Send(new byte[] {(byte) Command.GSGC_SETPEN, 0x01});

            return Send(new byte[] {(byte) Command.GSGC_SETPEN, 0x00});
        }

        /// <summary>
        ///   Print text to the screen.
        /// </summary>
        /// <param name = "text">The string you would like to print.</param>
        /// <param name = "color">Color of the string.</param>
        /// <param name = "p">Position of the top left of the string.</param>
        /// <param name = "font">Font type from Font enum.</param>
        /// <param name = "widthMult">Multipler of text width.</param>
        /// <param name = "heightMult">Multipler of text height.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool Print(string text, Color color, Point p = new Point(), Font font = Font.Font_5x7, int widthMult = 1,
                          int heightMult = 1)
        {
            byte[] textBytes = Encoding.UTF8.GetBytes(text);
            var data = new byte[9 + text.Length];

            data[0] = (byte) Command.GSGC_STRINGGFX; // draw string command 0x53
            data[1] = (byte) p.X; // set x
            data[2] = (byte) p.Y; // set y
            data[3] = (byte) font; // set font. default: 5x7 0x00                            
            data[4] = color.ByteColor[0]; // set color. default: 0xFF 0xFF (White)
            data[5] = color.ByteColor[1];
            data[6] = (byte) widthMult; // set the multiplier of the text width. default: 1
            data[7] = (byte) heightMult; // set the multiplier of the text height. default: 1

            for (int i = 0; i < textBytes.Length; i++)
                data[i + 8] = textBytes[i]; // add all the bytes from the string

            data[data.Length - 1] = 0x00; // Close the command by sending 0

            return Send(data);
        }

        /// <summary>
        ///   Draw a line from point 0 to point 1.
        /// </summary>
        /// <param name = "p0">Start location of the line.</param>
        /// <param name = "p1">End location of the line.</param>
        /// <param name = "color">Color of the line.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool DrawLine(Point p0, Point p1, Color color)
        {
            return Send(new[]
                            {
                                (byte) Command.GSGC_LINE,
                                (byte) p0.X,
                                (byte) p0.Y,
                                (byte) p1.X,
                                (byte) p1.Y,
                                color.ByteColor[0],
                                color.ByteColor[1]
                            });
        }

        /// <summary>
        ///   Draw a rectangle from topleft point  0 to bottom right point 1.
        /// </summary>
        /// <param name = "p0">Topleft point of rectangle.</param>
        /// <param name = "p1">Bottomright point of rectangle.</param>
        /// <param name = "color">Color of the rectangle.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool DrawRectangle(Point p0, Point p1, Color color)
        {
            return Send(new[]
                            {
                                (byte) Command.GSGC_RECTANGLE,
                                (byte) p0.X,
                                (byte) p0.Y,
                                (byte) p1.X,
                                (byte) p1.Y,
                                color.ByteColor[0],
                                color.ByteColor[1]
                            });
        }

        /// <summary>
        ///   Draw a polygon using the list of points.
        /// </summary>
        /// <param name = "points">List of points for polygon. has to be above 2 points.</param>
        /// <param name = "color">Color of the polygon.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool DrawPolygon(Point[] points, Color color)
        {
            if (points.Length < 3)
                return false;

            var bytes = new byte[points.Length*2 + 4];

            bytes[0] = (byte) Command.GSGC_POLYGON;
            bytes[1] = (byte) points.Length;

            int c = 2;
            for (int i = 0; i < points.Length; i++)
            {
                bytes[c] = (byte) points[i].X;
                c += 2;
            }
            c = 3;
            for (int i = 0; i < points.Length; i++)
            {
                bytes[c] = (byte) points[i].Y;
                c += 2;
            }

            bytes[bytes.Length - 2] = color.ByteColor[0];
            bytes[bytes.Length - 1] = color.ByteColor[1];

            return Send(bytes);
        }

        /// <summary>
        ///   Draw a circle.
        /// </summary>
        /// <param name = "center">The center location of the circle.</param>
        /// <param name = "color">Color of the circle.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool DrawCircle(Point center, int radius, Color color)
        {
            return Send(new[]
                            {
                                (byte) Command.GSGC_CIRCLE,
                                (byte) center.X,
                                (byte) center.Y,
                                (byte) radius,
                                color.ByteColor[0],
                                color.ByteColor[1]
                            });
        }

        /// <summary>
        ///   Draw a single pixel.
        /// </summary>
        /// <param name = "point">The pixel location on the screen.</param>
        /// <param name = "color">Color of the pixel.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool DrawPixel(Point point, Color color)
        {
            return Send(new[]
                            {
                                (byte) Command.GSGC_PIXEL,
                                (byte) point.X,
                                (byte) point.Y,
                                color.ByteColor[0],
                                color.ByteColor[1]
                            });
        }

        /// <summary>
        ///   Set the background color.
        /// </summary>
        /// <param name = "color">The background color.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool SetBackgroundColor(Color color)
        {
            return Send(new[]
                            {
                                (byte) Command.GSGC_BACKGND,
                                color.ByteColor[0],
                                color.ByteColor[1]
                            });
        }

        /// <summary>
        ///   Place an image on the screen.
        /// </summary>
        /// <param name = "bytemap">The byte amp of the image.</param>
        /// <param name = "point">Top left point of the image.</param>
        /// <param name = "width">The width of the image.</param>
        /// <param name = "height">The height of the image.</param>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool DrawImage(byte[] bytemap, Point point, int width, int height)
        {
            var bytes = new byte[bytemap.Length + 6];

            bytes[0] = (byte) Command.GSGC_IMG;

            bytes[1] = (byte) point.X;
            bytes[2] = (byte) point.Y;
            bytes[3] = (byte) width;
            bytes[4] = (byte) height;
            bytes[5] = 0x08;

            for (int i = 6; i < bytes.Length; i++)
                bytes[i] = bytemap[i - 6];

            return Send(bytes);
        }

        /// <summary>
        ///   Used to send raw byte arrays, so you can send your own extened commands.
        /// </summary>
        /// <returns>Whether the command was succesful or not.</returns>
        public bool Send(byte[] data)
        {
            _res[0] = 0;
            SerialCom.Write(data, 0, data.Length);

            SerialCom.Read(_res, 0, 1);

            return _res[0] == (byte) Response.ACK;
        }
    }
}